﻿//===================================================================
//
// Description: Sparqube Picture Column Lite for SharePoint 2010
// Web: http://www.sparqube.com/
//
//-------------------------------------------------------------------
//
// Copyright (C) 2012 ArtfulBits.CZ (development[at]artfulbits.cz)
// Web: http://www.artfulbits.cz/
//
//-------------------------------------------------------------------
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE.
//===================================================================

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;

using Microsoft.SharePoint;

namespace Sparqube.SharePoint.PictureColumnLite
{
	internal class FieldAdapter
	{
		#region Internal Classes

		private class PropertyMapItem
		{
			#region Constructors

			public PropertyMapItem(string propertyName, bool addAttribute) :
                this(propertyName, null, addAttribute)
			{
			}

            public PropertyMapItem(string propertyName, object defaultValue, bool addAttribute)
			{
				this.PropertyName = propertyName;
				this.DefaultValue = defaultValue;
                this.AddAttribute = addAttribute;
			}

			#endregion //Constructors

			#region Properties

			public string PropertyName { get; set; }
			public object DefaultValue { get; set; }
			public bool AddAttribute { get; set; }

			#endregion //Properties

		}// PropertyMapItem

		#endregion //Internal Classes

		#region Fields

		private SPField m_field = null;
		private bool m_isUpdating = false;
		private Dictionary<string, object> m_customProperties = null;
		private static Dictionary<int, Dictionary<string, object>> m_staticProperties;
		private List<PropertyMapItem> m_propertyMap = null;

		#endregion //Fields

		#region Constructors

		static FieldAdapter()
		{
			m_staticProperties = new Dictionary<int, Dictionary<string, object>>();
		}

		public FieldAdapter(SPField field)
		{
			m_field = field;
			m_propertyMap = new List<PropertyMapItem>();
			m_customProperties = new Dictionary<string, object>();
		}

		#endregion //Constructors

		#region Properties

		public SPField Field
		{
			get
			{
				return m_field;
			}
		}

		public int ContextId
		{
			get
			{
				SPContext context = SPContext.Current;
				return (null == context) ? 0 : context.GetHashCode();
			}
		}

		#endregion //Properties

		#region Methods

		public void Initialize()
		{
			foreach (PropertyMapItem property in m_propertyMap)
			{
				string propertyName = property.PropertyName;
				object val = null;
				try
				{
					val = this.Field.GetCustomProperty(propertyName);
				}
				catch { }
				m_customProperties[propertyName] = val;
			}
		}

		public void OnStartUpdate()
		{
			foreach (PropertyMapItem property in m_propertyMap)
			{
				string propertyName = property.PropertyName;
				object obj = GetProperty(propertyName);
				if (null != obj)
				{
					this.Field.SetCustomProperty(propertyName, obj);
				}
			}
			ConfigureSchema();
			m_isUpdating = false;
		}

		public void OnEndUpdate()
		{
			m_isUpdating = false;
			int contextId = this.ContextId;
			if (m_staticProperties.ContainsKey(contextId))
			{
				m_staticProperties.Remove(contextId);
			}
			Initialize();
		}

        public void OnUpdated()
        {
            if( !m_isUpdating )
            {
                if( null != this.Field.ParentList )
                {
                    bool fieldUpdating = m_isUpdating;
                    m_isUpdating = true;
                    MapToAttributes(this.Field);
                    if( !fieldUpdating )
                    {
                        m_isUpdating = false;
                    }
                }
            }
        }

		public void OnAdded()
		{
			this.Field.Update();
		}

		public void AddFieldProperty(string propertyName, bool addAttribute, object defaultValue)
		{
            PropertyMapItem property = new PropertyMapItem(propertyName, defaultValue, addAttribute);
			m_propertyMap.Add(property);
		}

		public object GetProperty(string propertyName)
		{
			object obj = null;
			Dictionary<string, object> properties = m_staticProperties.ContainsKey(this.ContextId) ?
				m_staticProperties[this.ContextId] : m_customProperties;
			if ((null != properties) && properties.ContainsKey(propertyName))
			{
				obj = properties[propertyName];
			}
			return obj;
		}

		public object GetProperty(string propertyName, object defaultValue)
		{
			object obj = GetProperty(propertyName);
			if (obj == null)
			{
				obj = defaultValue;
			}
			return obj;
		}

		public void SetProperty(string propertyName, object propertyValue, bool newField)
		{
			if (newField)
			{
				if (!m_staticProperties.ContainsKey(this.ContextId))
				{
					m_staticProperties.Add(this.ContextId, new Dictionary<string, object>());
				}
				m_staticProperties[this.ContextId][propertyName] = propertyValue;
			}
			else
			{
				m_customProperties[propertyName] = propertyValue;
			}
		}

		#endregion //Methods

		#region Implementation

		private static XmlNode GetFieldNode(SPField field)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(field.SchemaXml);
			return doc.FirstChild;
		}

		private void ConfigureSchema()
		{
			XmlNode xmlField = GetFieldNode(this.Field);
			if (null != this.Field.ParentList)
			{
				MapToAttributes(this.Field, xmlField);
			}
			else
			{
				RemoveAttributes(this.Field, xmlField);
			}
			UpdateSchema(this.Field, xmlField);
		}

		private static void MapToAttributes(SPField fieldHost)
		{
			SPList listPar = fieldHost.ParentList;
			if (null != listPar)
			{
				XmlNode xmlField = GetFieldNode(fieldHost);
				bool changed = false;
				SPWeb webPar = listPar.ParentWeb;
				SPSite site = webPar.Site;

				using (SPWeb web = site.OpenWeb(webPar.ID))
				{
					SPList list = web.Lists[listPar.ID];
					SPField field = list.Fields[fieldHost.Id];

					PictureLiteField column = field as PictureLiteField;
					if (column != null)
					{
						changed = ((PictureLiteField)field).Adapter.MapToAttributes(fieldHost, xmlField);
					}
				}

				if (changed)
				{
					UpdateSchema(fieldHost, xmlField);
				}
			}
		}

		private bool MapToAttributes(SPField field, XmlNode xmlField)
		{
			bool changed = false;
			bool isFieldAddedToList = field.ParentList != null;
			foreach (PropertyMapItem property in m_propertyMap)
			{
				if(isFieldAddedToList && property.AddAttribute)
				{
					string propertyName = property.PropertyName;
					object propertyValue = GetProperty(propertyName);
					if (propertyValue == null)
					{
						propertyValue = property.DefaultValue;
					}
					SetAttribute(xmlField, propertyName, propertyValue);
				}

				changed = true;
			}
			return changed;
		}

		private bool RemoveAttributes(SPField field, XmlNode xmlField)
		{
			bool changed = false;
			foreach (PropertyMapItem property in m_propertyMap)
			{
				string propertyName = property.PropertyName;
                SetAttribute(xmlField, propertyName, null);
				changed = true;
			}
			return changed;
		}

		private static bool GetIsFieldInitialized(SPField field)
		{
			bool res = false;
			try
			{
				Guid id = field.Id;
				res = !Guid.Empty.Equals(id);
			}
			catch{}
			return res;
		}

		private static void UpdateSchema(SPField field, XmlNode xmlField)
		{
			if (GetIsFieldInitialized(field))
			{
				field.SchemaXml = xmlField.OwnerDocument.OuterXml;
			}
		}

		private static void SetAttribute(XmlNode xmlField, string attrName, object attrValue)
		{
			XmlAttribute xmlAttribute = xmlField.Attributes[attrName];
			if (xmlAttribute == null)
			{
				if (null != attrValue)
				{
					XmlAttribute attrib = xmlField.OwnerDocument.CreateAttribute(attrName);
					attrib.Value = ConvertToAttributeValue(attrValue);
					xmlField.Attributes.Append(attrib);
				}
			}
			else
			{
				if (null != attrValue)
				{
					xmlAttribute.Value = ConvertToAttributeValue(attrValue);
				}
				else
				{
					xmlField.Attributes.Remove(xmlAttribute);
				}
			}
		}

		private static string ConvertToAttributeValue(object value)
		{
			string res = "";
			if (null != value)
			{
				if (value is bool)
				{
					res = ((bool)value).ToString(CultureInfo.InvariantCulture).ToUpperInvariant();
				}
				else if (value is Enum)
				{
					res = ((int)value).ToString(CultureInfo.InvariantCulture);
				}
				else
				{
					res = value.ToString();
				}
			}
			return res;
		}

		#endregion //Implementation

	}// FieldAdapter
}